﻿using System;
using System.Collections.Generic;
using NJabber.Stream;
using NJabber.Stream.Elements;

namespace NJabber.Client
{
    public class XmppClient
    {
        private IClientRuntimeFactory runtimeFactory;
        private XmppClientRuntime runtime;
        
        public XmppClient() : this(StandartClientRuntimeFactory.Default)
        {
        }

        public XmppClient(IClientRuntimeFactory runtimeFactory)
        {
            this.runtimeFactory = runtimeFactory;
        }

        public bool IsConnected
        {
            get { return Connection.State == ConnectionState.Opened; }
        }

        public JID Jid { get; set; }

        public IRosterCache Roster
        {
            get{ return Runtime.RosterCache; }
        }

        public IEnumerable<RosterItem> RosterItems
        {
            get { return Roster.Items; }
        }

        protected IXmppConnection Connection
        {
            get { return Runtime.Connection; }
        }

        protected XmppClientRuntime Runtime
        {
            get { return runtime ?? (runtime = CreateRuntime()); }
        }

        protected IStanzaStream StanzaStream
        {
            get { return Connection.Stream; }
        }

        public void Connect()
        {
            Connection.Open();
            StanzaStream.StanzaReceived += ProcessReceivedStanza;
        }

        protected virtual XmppClientRuntime CreateRuntime()
        {
            return runtimeFactory.CreateRuntime();
        }

        private void ProcessReceivedStanza(object sender, StanzaReceivedEventArgs args)
        {
            foreach (IStanzaHandler handler in Runtime.StanzaHandlers)
            {
                if (!handler.ProcessStanza(Runtime, args.Stanza)) break;
            }
        }

        public void SendStanza(Stanza stanza)
        {
            if (!IsConnected) throw new InvalidOperationException();
            if (stanza == null) throw new NullReferenceException(); 
            StanzaStream.Write(stanza);
        }

        #region Presence managment

        public PresenceStatus PresenceStatus { get; set; }

        public PresenceStatus Presence(JID entityJid)
        {
            return Runtime.PresenceStatusProvider[entityJid];
        }

        public void UpdatePresence()
        {
            UpdatePresence(PresenceStatus);
        }

        public void UpdatePresence(PresenceStatus status)
        {
            Stanza stanza = Runtime.PresenceStanzaFactory.CreateAvailabilityMessage(status.IsAvailable,
                                                                            status.AvailabilityStatus,
                                                                            status.AvailabilityDescription);
            StanzaStream.Write(stanza);
        }

        #endregion

        #region Subscription managment

        public void SendSubscriptionRequest(JID subscriptionTarget)
        {
            if (subscriptionTarget == null) throw new ArgumentNullException("subscriptionTarget");
            Stanza subscriptionRequest = CreateSubscriptionRequest(subscriptionTarget);
            StanzaStream.Write(subscriptionRequest);
        }

        private Stanza CreateSubscriptionRequest(JID subscriptionTarget)
        {
            PresenceStanza subscriptionRequest = new PresenceStanza();
            subscriptionRequest.Type = PresenceStanzaType.Subscribe;
            subscriptionRequest.To = subscriptionTarget;
            return subscriptionRequest;
        }

        #endregion

        private void OnIqReceived(IqStanza iq)
        {
            if (IqReceived != null) IqReceived(this, new IqReceivedEventArgs(iq));
        }

        private void OnMessageReceived(MessageStanza message)
        {
            if (MessageReceived != null) MessageReceived(this, new MessageReceivedEventArgs(message));
        }

        private void OnPresenceReceived(PresenceStanza presence)
        {
            if (PresenceReceived != null) PresenceReceived(this, new PresenceReceivedEventArgs(presence));
        }

        public event EventHandler<IqReceivedEventArgs> IqReceived;

        public event EventHandler<MessageReceivedEventArgs> MessageReceived;

        public event EventHandler<PresenceReceivedEventArgs> PresenceReceived;
    }
}
